#include "Terrain/TerrainBrush.h"
#include "Engine/MathUtils.hpp"
#include "Engine/Vector3.hpp"
#include "Terrain/Terrain.h"
#include "Terrain/TerrainChunk.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Log.h"

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    void TerrainBrush::compile()
    {
        isCompile = true;
    }
    int32_t TerrainBrush::countNonDiagonalNeighbors(int32_t x, int32_t y, int32_t z, TBCounter *counter)
    {
        return  0 + counter->count(this, Point3(x - 1, y, z)) + counter->count(this, Point3(x + 1, y, z)) + 
                    counter->count(this, Point3(x, y - 1, z)) + counter->count(this, Point3(x, y + 1, z)) + 
                    counter->count(this, Point3(x, y, z - 1)) + counter->count(this, Point3(x, y, z + 1));
    }
    void TerrainBrush::replace(int32_t oldValue, int32_t newValue)
    {
        for(auto &pair : cellsMap)
        {
            if(pair.second.value == oldValue)
            {
                pair.second.value = newValue;
            }
        }
    }
    void TerrainBrush::calculateBounds(Point3 &min, Point3 &max)
    {
		min = Point3(0);
		max = Point3(0);
		bool flag = true;
        for(auto &pair : cellsMap)
        {
			if (flag)
			{
				flag = false;
                
				min.posX = pair.second.posX;
				min.posY = pair.second.posY;
				min.posZ = pair.second.posZ;

                max.posX = pair.second.posX;
                max.posY = pair.second.posY;
                max.posZ = pair.second.posZ;

			}
			else
			{
				min.posX = MathUtils::Min<int32_t>(min.posX, pair.second.posX);
				min.posY = MathUtils::Min<int32_t>(min.posY, pair.second.posY);
				min.posZ = MathUtils::Min<int32_t>(min.posZ, pair.second.posZ);
				max.posX = MathUtils::Max<int32_t>(max.posX, pair.second.posX);
				max.posY = MathUtils::Max<int32_t>(max.posY, pair.second.posY);
				max.posZ = MathUtils::Max<int32_t>(max.posZ, pair.second.posZ);
			}
        }
    }
    int32_t TerrainBrush::getValue(int32_t x, int32_t y, int32_t z)
    {
        auto it = cellsMap.find(key(x, y, z));
        if(it != cellsMap.end())
        {
            return it->second.value;
        }
        return -1;
    }
    void TerrainBrush::addCell(int32_t x, int32_t y, int32_t z, TBBrush *brush)
    {
        if(isCompile == true)
        {
            isCompile = false;
            cellsMap.clear();
        }
        int32_t num = brush->paint(this, Point3(x, y, z));
        if(num != -1)
        {
            cellsMap[key(x, y, z)] = TBCell
            {
                (int8_t)x,
                (int8_t)y,
                (int8_t)z,
                num
            };
        }
    }
    void TerrainBrush::addBox(int32_t x, int32_t y, int32_t z, int32_t sizeX, int32_t sizeY, int32_t sizeZ, TBBrush *brush)
    {
		for (int32_t i = x; i < x + sizeX; i++)
		{
			for (int32_t j = y; j < y + sizeY; j++)
			{
				for (int32_t k = z; k < z + sizeZ; k++)
				{
					addCell(i, j, k, brush);
				}
			}
		}
    }
    void TerrainBrush::addRay(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2, int32_t sizeX, int32_t sizeY, int32_t sizeZ, TBBrush *brush)
    {
		Vector3 vector = Vector3(x1, y1, z1) + 0.5f;
		Vector3 vector2 = Vector3(x2, y2, z2) + 0.5f;
		Vector3 vector3 = 0.33f * Vector3::Normalize(vector2 - vector);
		int32_t num = (int32_t)MathUtils::Round(3.0f * Vector3::Distance(vector, vector2));
		Vector3 vector4 = vector;
		for (int32_t i = 0; i < num; i++)
		{
			int32_t x3 = Terrain::ToCell(vector4.posX);
			int32_t y3 = Terrain::ToCell(vector4.posY);
			int32_t z3 = Terrain::ToCell(vector4.posZ);
			addBox(x3, y3, z3, sizeX, sizeY, sizeZ, brush);
			vector4 += vector3;
		}
    }
    void TerrainBrush::paintFastSelective(TerrainChunk *chunk, int32_t x, int32_t y, int32_t z, int32_t onlyInValue)
    {
		x -= chunk->origin.posX;
		z -= chunk->origin.posY;
        for(const auto& pair : cellsMap)
        {
            int32_t num = pair.second.posX + x;
            int32_t num2 = pair.second.posY + y;
            int32_t num3 = pair.second.posZ + z;
            if(num >= 0 && num < 16 && num2 >= 0 && num2 < 256 && num3 >= 0 && num3 < 16)
            {
                int32_t index = TerrainChunk::CalculateCellIndex(num, num2, num3);
                int32_t cellValueFast = chunk->getCellValueFast(index);
                if(onlyInValue == cellValueFast)
                {
                    chunk->setCellValueFast(index, pair.second.value);
                }
            }
        }
    }
    void TerrainBrush::paintFastAvoidWater(SubsystemTerrain *terrain, TerrainChunk *chunk, int32_t x, int32_t y, int32_t z)
    {
		x -= chunk->origin.posX;
		z -= chunk->origin.posY;
        for(const auto &pair : cellsMap)
        {
			int32_t num =  pair.second.posX + x;
			int32_t num2 = pair.second.posY + y;
			int32_t num3 = pair.second.posZ + z;
			if (num >= 0 && num < 16 && num2 >= 0 && num2 < 255 && num3 >= 0 && num3 < 16)
			{
                if (chunk->getCellContentsFast(num, num2, num3) != 18 &&
                    terrain->getCellContents(num - 1, num2, num3) != 18 &&
                    terrain->getCellContents(num + 1, num2, num3) != 18 && 
                    terrain->getCellContents(num, num2, num3 - 1) != 18 && 
                    terrain->getCellContents(num, num2, num3 + 1) != 18 && 
                    chunk->getCellContentsFast(num, num2 + 1, num3) != 18)
                {
                    chunk->setCellValueFast(num, num2, num3, pair.second.value);
                }
			}
        }
    }
    void TerrainBrush::paintFast(TerrainChunk *chunk, int32_t x, int32_t y, int32_t z)
    {
		x -= chunk->origin.posX;
		z -= chunk->origin.posY;
        for(const auto& pair : cellsMap)
        {
			int32_t num  = pair.second.posX + x;
			int32_t num2 = pair.second.posY + y;
			int32_t num3 = pair.second.posZ + z;
			if (num >= 0 && num < 16 && num2 >= 0 && num2 < 256 && num3 >= 0 && num3 < 16)
			{
				chunk->setCellValueFast(num, num2, num3, pair.second.value);
			}
        }
    }
    void TerrainBrush::paint(SubsystemTerrain *terrain, int32_t x, int32_t y, int32_t z)
    {
        for(const auto &pair : cellsMap)
        {
			int32_t x2 = pair.second.posX + x;
			int32_t y2 = pair.second.posY + y;
			int32_t z2 = pair.second.posZ + z;
			terrain->changeCell(x2, y2, z2, pair.second.value);
        }
    }



} // namespace PocketSurvival
